<?php
namespace ZyucMini\Core;

/**
 * 日志类 - 负责应用程序日志记录
 * 提供多级别日志记录、按日分文件、安全写入等功能
 */
class Logger {
    /**
     * 日志级别常量
     */
    const DEBUG = 'DEBUG';
    const INFO = 'INFO';
    const WARNING = 'WARNING';
    const ERROR = 'ERROR';
    const CRITICAL = 'CRITICAL';
    
    /**
     * 日志级别优先级（数字越小优先级越高）
     */
    const LEVELS = [
        self::DEBUG => 0,
        self::INFO => 1,
        self::WARNING => 2,
        self::ERROR => 3,
        self::CRITICAL => 4
    ];

    /**
     * 日志目录路径
     * @var string
     */
    private $logDir;
    
    /**
     * 当前日志级别
     * @var string
     */
    private $level;
    
    /**
     * 是否启用控制台输出
     * @var bool
     */
    private $consoleOutput;

    /**
     * 构造函数
     * @param string $logDir 日志目录名称
     * @param string $level 日志级别
     * @param bool $consoleOutput 是否启用控制台输出
     */
    public function __construct($logDir = 'logs', $level = self::INFO, $consoleOutput = null) {
        $this->setLogDir($logDir);
        $this->level = $level;
        $this->consoleOutput = $consoleOutput ?? (php_sapi_name() === 'cli');
    }

    /**
     * 设置日志目录
     * @param string $dir 日志目录名称
     * @throws \Exception
     */
    public function setLogDir($dir) {
        $this->validatePath($dir);
        $this->logDir = rtrim($dir, '/');
        
        // 确保目录存在
        if (!is_dir($this->logDir)) {
            if (!mkdir($this->logDir, 0755, true)) {
                throw new \Exception("Logger Error: failed to create log directory: {$this->logDir}");
            }
        }
        
        // 检查目录是否可写
        if (!is_writable($this->logDir)) {
            throw new \Exception("Logger Error: log directory is not writable: {$this->logDir}");
        }
    }

    /**
     * 记录日志
     * @param string $level 日志级别
     * @param string $message 日志消息
     * @param array $context 上下文信息
     * @throws \Exception
     */
    public function log($level, $message, $context = []) {
        // 检查日志级别
        if (!$this->shouldLog($level)) {
            return;
        }
        
        // 格式化日志消息
        $formattedMessage = $this->formatMessage($level, $message, $context);
        
        // 控制台输出
        if ($this->consoleOutput) {
            $this->outputToConsole($level, $formattedMessage);
        }
        
        // 写入日志文件
        $this->writeToFile($formattedMessage);
    }

    /**
     * 记录DEBUG级别日志
     * @param string $message 日志消息
     * @param array $context 上下文信息
     */
    public function debug($message, $context = []) {
        $this->log(self::DEBUG, $message, $context);
    }

    /**
     * 记录INFO级别日志
     * @param string $message 日志消息
     * @param array $context 上下文信息
     */
    public function info($message, $context = []) {
        $this->log(self::INFO, $message, $context);
    }

    /**
     * 记录WARNING级别日志
     * @param string $message 日志消息
     * @param array $context 上下文信息
     */
    public function warning($message, $context = []) {
        $this->log(self::WARNING, $message, $context);
    }

    /**
     * 记录ERROR级别日志
     * @param string $message 日志消息
     * @param array $context 上下文信息
     */
    public function error($message, $context = []) {
        $this->log(self::ERROR, $message, $context);
    }

    /**
     * 记录CRITICAL级别日志
     * @param string $message 日志消息
     * @param array $context 上下文信息
     */
    public function critical($message, $context = []) {
        $this->log(self::CRITICAL, $message, $context);
    }
    
    /**
     * 设置日志级别
     * @param string $level 日志级别
     * @throws \InvalidArgumentException
     */
    public function setLevel($level) {
        if (!isset(self::LEVELS[$level])) {
            throw new \InvalidArgumentException("Invalid log level: {$level}");
        }
        $this->level = $level;
    }
    
    /**
     * 获取当前日志级别
     * @return string
     */
    public function getLevel() {
        return $this->level;
    }
    
    /**
     * 检查是否应该记录指定级别的日志
     * @param string $level 日志级别
     * @return bool
     */
    protected function shouldLog($level) {
        return self::LEVELS[$level] >= self::LEVELS[$this->level];
    }
    
    /**
     * 格式化日志消息
     * @param string $level 日志级别
     * @param string $message 日志消息
     * @param array $context 上下文信息
     * @return string
     */
    protected function formatMessage($level, $message, $context = []) {
        $datetime = date('Y-m-d H:i:s');
        $formatted = "[{$datetime}] {$level}: {$message}";
        
        // 处理上下文信息
        if (!empty($context)) {
            $formatted .= ' ' . json_encode($context, JSON_UNESCAPED_UNICODE);
        }
        
        return $formatted . PHP_EOL;
    }
    
    /**
     * 输出到控制台
     * @param string $level 日志级别
     * @param string $message 格式化后的消息
     */
    protected function outputToConsole($level, $message) {
        echo $message;
    }
    
    /**
     * 写入日志文件
     * @param string $message 格式化后的消息
     * @throws \Exception
     */
    protected function writeToFile($message) {
        $logFile = $this->getLogFilePath();
        
        // 写入文件
        if (file_put_contents($logFile, $message, FILE_APPEND | LOCK_EX) === false) {
            throw new \Exception("Logger Error: failed to write to log file: {$logFile}");
        }
    }
    
    /**
     * 获取日志文件路径（按日期分文件）
     * @return string
     */
    protected function getLogFilePath() {
        $date = date('Y-m-d');
        return $this->logDir . '/' . $date . '.log';
    }
    
    /**
     * 验证路径安全性
     * @param string $path 路径
     * @throws \Exception
     */
    protected function validatePath($path) {
        if (empty($path)) {
            throw new \Exception("Logger Error: log directory path cannot be empty");
        }
        
        // 检查路径遍历攻击
        if (strpos($path, '..') !== false) {
            throw new \Exception("Logger Error: path traversal detected in: {$path}");
        }
        
        // 检查绝对路径
        $realPath = realpath(dirname($path));
        if ($realPath === false) {
            throw new \Exception("Logger Error: invalid path: {$path}");
        }
    }
    
    /**
     * 清理旧日志文件
     * @param int $days 保留天数
     * @return int 删除的文件数量
     */
    public function cleanOldLogs($days = 30) {
        $cutoffTime = time() - ($days * 24 * 60 * 60);
        $deletedCount = 0;
        
        $files = glob($this->logDir . '/*.log');
        foreach ($files as $file) {
            if (filemtime($file) < $cutoffTime) {
                if (unlink($file)) {
                    $deletedCount++;
                }
            }
        }
        
        return $deletedCount;
    }
    
    /**
     * 获取日志统计信息
     * @return array
     */
    public function getStats() {
        $stats = [
            'logDir' => $this->logDir,
            'level' => $this->level,
            'consoleOutput' => $this->consoleOutput,
            'totalFiles' => 0,
            'totalSize' => 0
        ];
        
        $files = glob($this->logDir . '/*.log');
        foreach ($files as $file) {
            $stats['totalFiles']++;
            $stats['totalSize'] += filesize($file);
        }
        
        return $stats;
    }
}